In May 2020, the Georgia Department of Public Health posted the following plot to illustrate the number of confirmed COVID-19 cases in their hardest-hit counties over a two-week period. Health officials claimed that the plot provided evidence that COVID-19 cases were decreasing and made the argument for reopening the state.
The plot was heavily criticized by the statistical community and several media outlets for its deceptive portrayal of COVID-19 trends in Georgia. Whether the end result was due to malicious intent or simply poor judgment, it is incredibly irresponsible to publish data visualizations that obscure and distort the truth.
Data visualization is an incredibly powerful tool that can affect health policy decisions. Ensuring they are easy to interpret, and more importantly, showcase accurate insights from data is paramount for scientific transparency and the health of individuals. For this assignment you are tasked with reproducing COVID-19 visualizations and tables published by the New York Times. Specifically, you will attempt to reproduce the following for January 12th, 2022:
Data for cases and deaths can be downloaded from this NYT GitHub
repository (use us-counties.csv). Data for county
populations can be downloaded from The
US Census Bureau. We will provide code for wrangling population data
and date to plot the map in Task #3.
The project must be submitted in the form of a Jupyter notebook or RMarkdown file and corresponding compiled/knitted PDF, with commented code and text interspersed, including a brief critique of the reproducibility of each plot and table. All project documents must be uploaded to a GitHub repository each student will create within the reproducible data science organization. The repository must also include a README file describing the contents of the repository and how to reproduce all results. You should keep in mind the file and folder structure we covered in class and make the reproducible process as automated as possible.
Tips:
lag function. In this toy example, cases records
the daily total/cumulative number of cases over a two-week period. By
default, the lag function simply shifts the vector of cases back by one.
The number of new cases on each day is then the difference between
cases and lag(cases).cases = c(13, 15, 18, 22, 29, 39, 59, 61, 62, 67, 74, 89, 108, 122)
new_cases = cases - lag(cases)
new_cases
## [1] NA 2 3 4 7 10 20 2 1 5 7 15 19 14
zoo package already provides the
rollmean function. Below, the k = 7 argument
tells the function to use a rolling window of seven entries.
fill = NA tells rollmean to return
NA for days where the seven-day rolling average can’t be
calculated (e.g. on the first day, there are no days that come before,
so the sliding window can’t cover seven days). That way,
new_cases_7dayavg will be the same length as
cases and new_cases, which would come in handy
if they all belonged to the same data frame.library(zoo)
new_cases_7dayavg = rollmean(new_cases, k = 7, fill = NA)
new_cases_7dayavg
## [1] NA NA NA NA 6.857143 6.714286 7.000000 7.428571
## [9] 8.571429 9.857143 9.000000 NA NA NA
Create the new cases as a function of time with a rolling average plot - the first plot on the page (you don’t need to recreate the colors or theme).
Code to read in the data and get you started.
# Read in NYT data
# Note that this is read in using a URL but the csv can also be saved and used.
nyt <- read.csv(url("https://raw.githubusercontent.com/nytimes/covid-19-data/master/us-counties.csv"))
dim(nyt)
## [1] 2502832 6
head(nyt)
## date county state fips cases deaths
## 1 2020-01-21 Snohomish Washington 53061 1 0
## 2 2020-01-22 Snohomish Washington 53061 1 0
## 3 2020-01-23 Snohomish Washington 53061 1 0
## 4 2020-01-24 Cook Illinois 17031 1 0
## 5 2020-01-24 Snohomish Washington 53061 1 0
## 6 2020-01-25 Orange California 6059 1 0
# create data set for task 1
df_task1 <- nyt %>%
group_by(date) %>% arrange(date) %>% summarise(
# calculate cumulative cases and deaths for the U.S. on each day
# ignore missing values
all_cases = sum(cases, na.rm = T),
all_deaths = sum(deaths, na.rm = T)) %>%
mutate(
# calculate new cases and deaths each day
new_cases = all_cases - lag(all_cases),
new_deaths = all_deaths - lag(all_deaths)) %>%
# separate the date column into year, month and day in order to filter
# out the date during Mar. 2020 -- Jan. 2021
separate(date, into = c("year", "month", "day")) %>%
filter( ((year == "2020")&(!(month %in% c("01", "02")))) |
((year == "2021")&(month == "01")&(day <= 18)) ) %>%
# calculate a seven-day rolling average, and return NA for days where
# the seven-day rolling average can't be calculated
mutate(new_cases_7dayavg = rollmean(new_cases, k = 7, fill = NA),
new_deaths_7dayavg = rollmean(new_deaths, k = 7, fill = NA)
) %>%
# put the date back again
unite(col = date, year, month, day, sep = "-")
## create the plot
plot_task1 <- ggplot(df_task1) +
# create the histogram for new cases each day
geom_histogram(aes(x = date, y = new_cases, fill = "pink", alpha = 0.6),
stat = "identity") +
# create the 7-day rolling average
geom_line(aes(x = date, y = new_cases_7dayavg, group = 1), color = "firebrick3",
method = "loess", se = F, size = 0.8) +
# create title and label x- and y-axis
labs(title = "Coronavirus in the U.S.: Latest Map and Case Count",
subtitle = "Updated January 18, 2021",
y = "Cases", x = "Date") +
scale_x_discrete(
breaks = c("2020-03-01", "2020-04-01", "2020-05-01", "2020-06-01", "2020-07-01",
"2020-08-01", "2020-09-01", "2020-10-01", "2020-11-01", "2020-12-01",
"2021-01-01"),
labels = c("Mar.2020", "Apr.", "May", "Jun.", "Jul.", "Aug.", "Sept.", "Oct.",
"Nov.", "Dec.", "Jan.2021"), expand = c(0,0)) +
scale_y_continuous(
breaks = c(0, 100000, 200000, 300000),
labels = c("0", "100,000", "200,000", "300,000 cases"),
expand = c(0,0), limits = c(0, 350000)
) +
theme_bw() +
theme(panel.grid.major.x = element_blank(),
panel.grid.major.y = element_line(linetype = "dashed", color = "grey",
size = 0.3),
axis.ticks.y = element_blank(),
panel.grid.minor.y = element_blank(),
panel.border = element_blank(),
axis.line.x = element_line(colour = "gray", size = 0.3, linetype = "solid"),
legend.position = "none") +
# add text annotation about the line and histogram
geom_text(x = "2020-07-23",
y = df_task1$new_cases[df_task1$date == "2020-07-23"]+20000,
label = "7-day average") +
geom_text(x = "2020-12-10",
y = 300000,
label = "New cases") +
geom_segment(x = "2020-07-23", xend = "2020-07-23",
y = df_task1$new_cases_7dayavg[df_task1$date == "2020-07-23"],
yend = df_task1$new_cases[df_task1$date == "2020-07-23"]+10000) +
geom_segment(x = "2021-01-01", xend = df_task1$date[which.max(df_task1$new_cases)],
y = 300000, yend = 300000)
plot_task1
# save the plot that I reproduced
ggsave(filename = "task1.png", plot = plot_task1,
path = "reproduced_figures",
width = 20,
height = 10,
units = c("cm"))
Create the table of cases and deaths - the first table on the page, right below the figure you created in task #1. You don’t need to include tests or hospitalizations.
# create data set for task 2 - new cases and death on Jan. 17, 2021
df_task2_jan17 <- df_task1 %>% filter(date == "2021-01-17") %>%
summarize(cases = sum(new_cases),
deaths = sum(new_deaths))
# create data set for task 2 - cumulative cases and deaths on Jan. 17, 2021
df_task2_total <- nyt %>% filter(date == "2021-01-17") %>%
summarize(cases = sum(cases, na.rm = T), deaths = sum(deaths, na.rm = T))
# create data set for task 2 - 14-day change on Jan. 17, 2021
df_task2_14dchange <- rbind(
# 14 day change = 7-day change on (2021-01-14 - 2020-12-30)/2021-01-14
paste(round(100*(df_task1$new_cases_7dayavg[df_task1$date == "2021-01-14"] -
df_task1$new_cases_7dayavg[df_task1$date =="2020-12-30"])/
df_task1$new_cases_7dayavg[df_task1$date == "2021-01-14"],0), "%", sep = ""),
paste(round(100*(df_task1$new_deaths_7dayavg[df_task1$date == "2021-01-14"] -
df_task1$new_deaths_7dayavg[df_task1$date =="2020-12-30"])/
df_task1$new_deaths_7dayavg[df_task1$date == "2021-01-14"],0), "%", sep = "")
)
# create table for task 2
df_task2 <- data.frame(total = format(t(df_task2_total), format = "d", big.mark=","),
jan17 = format(t(df_task2_jan17), format ="d",big.mark=","),
change = df_task2_14dchange)
colnames(df_task2) <- c("Total reported", "On Jan. 17", "14-Day change")
rownames(df_task2) <- c("Cases", "Deaths")
table_task2 <- df_task2 %>% kable("html") %>% kable_classic() %>%
row_spec(0, bold = T) %>% column_spec(1, bold = T) %>%
kable_styling(full_width = F)
table_task2
| Total reported | On Jan. 17 | 14-Day change | |
|---|---|---|---|
| Cases | 23,986,856 | 170,094 | 6% |
| Deaths | 397,624 | 1,730 | 21% |
# save the table
save_kable(table_task2, "reproduced_figures/task2.png", zoom = 1.5)
Create the county-level map for previous week (‘Hot spots’) - the second plot on the page (only the ‘Hot Spots’ plot). You don’t need to include state names and can use a different color palette.
Code to wrangle county population data and map data.
# Get US county populations from census
county_pop <- as.data.frame(data.table::fread("https://www2.census.gov/programs-surveys/popest/datasets/2010-2019/counties/totals/co-est2019-alldata.csv"))
# Wrangle data and pull population estimates from 2019
county_pop <- county_pop %>%
mutate(STNAME = str_to_lower(STNAME),
CTYNAME = str_replace(CTYNAME, "\\sCounty|\\sParish", ""),
CTYNAME = str_replace(CTYNAME, "\\.", ""),
CTYNAME = str_to_lower(CTYNAME),) %>%
select(STNAME, CTYNAME, POPESTIMATE2019) %>%
rename(region = STNAME, subregion = CTYNAME, population = POPESTIMATE2019)
head(county_pop)
## region subregion population
## 1 alabama alabama 4903185
## 2 alabama autauga 55869
## 3 alabama baldwin 223234
## 4 alabama barbour 24686
## 5 alabama bibb 22394
## 6 alabama blount 57826
# Load map data (US counties)
library(usmap)
library(maps)
##
## Attaching package: 'maps'
## The following object is masked from 'package:purrr':
##
## map
counties <- map_data("county")
head(counties)
## long lat group order region subregion
## 1 -86.50517 32.34920 1 1 alabama autauga
## 2 -86.53382 32.35493 1 2 alabama autauga
## 3 -86.54527 32.36639 1 3 alabama autauga
## 4 -86.55673 32.37785 1 4 alabama autauga
## 5 -86.57966 32.38357 1 5 alabama autauga
## 6 -86.59111 32.37785 1 6 alabama autauga
# Merge map data frame and population data frame
counties <- counties %>%
left_join(county_pop, by = c("region", "subregion"))
head(counties)
## long lat group order region subregion population
## 1 -86.50517 32.34920 1 1 alabama autauga 55869
## 2 -86.53382 32.35493 1 2 alabama autauga 55869
## 3 -86.54527 32.36639 1 3 alabama autauga 55869
## 4 -86.55673 32.37785 1 4 alabama autauga 55869
## 5 -86.57966 32.38357 1 5 alabama autauga 55869
## 6 -86.59111 32.37785 1 6 alabama autauga 55869
# Wrangle NYT data to match counties data frame.
nyt <- nyt %>% rename(region = state,
subregion = county) %>%
mutate(region = str_to_lower(region),
subregion = str_to_lower(subregion),
subregion = str_replace(subregion, "\\.", ""),)
head(nyt)
## date subregion region fips cases deaths
## 1 2020-01-21 snohomish washington 53061 1 0
## 2 2020-01-22 snohomish washington 53061 1 0
## 3 2020-01-23 snohomish washington 53061 1 0
## 4 2020-01-24 cook illinois 17031 1 0
## 5 2020-01-24 snohomish washington 53061 1 0
## 6 2020-01-25 orange california 6059 1 0
# Calculate average daily cases for the plot - remember to group by region, subregion, and date. Then filter to only include the date 2022-01-12.
# Your code here
# Merge your updated nyt data frame and counties data frame by joining by region and subregion.
Mapping US state counties is possible using the maps
package by using map_data("county"). Here we choose a red
outline and no fill color. You will need to fill the counties with the
average number of daily cases per capita and can change the outline
color to white.
AllCounty <- map_data("county")
AllCounty %>% ggplot(aes(x = long, y = lat, group = group)) +
geom_polygon(color = "red", fill = NA, size = .1 )
Create the table of cases by state - the second table on the page (do not need to include per 100,000, 14-day change, or fully vaccinated columns).
Provide a brief critique of the reproducibility of the figures and tables you created in tasks 1-4.